//
// Created by Alex on 17/3/8.
//

#ifndef LIBCR_LIBCR_H
#define LIBCR_LIBCR_H

#include <cstdlib>
#include <vector>
#include <list>
#include <cstdint>
#include <unistd.h>
#include <functional>

struct uthread_t;
struct share_stack_t;

struct uthread_attr_t {
    int stack_size;
    share_stack_t *share_stack;

    uthread_attr_t() : stack_size(-1), share_stack(nullptr) {}
};

struct cr_cond_t {
    std::vector<void *> m_list;
};

void cr_create(uthread_t *&ut, uthread_attr_t *attr, void (*fun)(void *), void *arg);

void cr_resume(uthread_t *ut);

void cr_yield();

int cr_accept(int fd, struct sockaddr *cliAddr, socklen_t *addrLen, uint64_t timeout = 0);

void *cr_get_sepc(uthread_t *ut, int key);

void cr_set_spec(uthread_t *ut, int key, void *value);

void cr_release(uthread_t *ut);

uthread_t *cr_get_curr_uthread();

void cr_scheduler();

bool cr_poll(int fd, int &events, uint64_t timeout = 0);

ssize_t cr_write(int fd, const void *buf, size_t count, uint64_t timeout = 0);

ssize_t cr_read(int fd, void *buf, size_t count, uint64_t timeout = 0);

void cr_sleep(uint64_t timeout_ms);

ssize_t cr_readv(int fd, const struct iovec *iov, int iovcnt);

ssize_t cr_writev(int fd, const struct iovec *iov, size_t iovcnt);

ssize_t cr_writev_all(int fd, struct iovec *iov, size_t iovcnt);

int cr_connect(int fd, const struct sockaddr *addr, socklen_t addrLen, uint64_t timeout);

int cr_recvfrom(int fd, void *buf, size_t size);

ssize_t cr_sendto(int socket, const void *message, size_t length,
                  int flags, const struct sockaddr *dest_addr,
                  socklen_t dest_len, uint64_t timeout = 0);

ssize_t cr_recvfrom(int socket, void *buffer, size_t length,
                    int flags, struct sockaddr *address,
                    socklen_t *address_len, uint64_t timeout = 0);

int cr_init(int fd);

void cr_enable_hook();

void cr_disable_hook();

void cr_cond_signal(cr_cond_t *cond);

void cr_cond_broadcast(cr_cond_t *cond);

void cr_cond_timedwait(cr_cond_t *cond, uint64_t ms);

share_stack_t *cr_create_share_stack(int32_t count, int32_t stack_size);

void cr_release_share_stack(share_stack_t *share);

template<typename Type>
struct Channel {
    typename std::list<Type> m_data;
    size_t m_size;
    size_t m_capacity;
    cr_cond_t m_fullCond;
    cr_cond_t m_emptyCond;

    Channel(size_t count = 1) : m_capacity(count), m_size(0) {}

    void Put(const Type type) {
        while (1) {
            if (m_size <= m_capacity) {
                m_data.push_back(type);
                m_size += 1;
                cr_cond_signal(&m_emptyCond);

                break;
            } else {
                cr_cond_timedwait(&m_fullCond, 0);
            }
        }
    }

    Type Get() {
        while (1) {
            if (m_size > 0) {
                Type type = m_data.front();
                m_data.pop_front();
                m_size -= 1;

                cr_cond_signal(&m_fullCond);
                return type;

            } else {
                cr_cond_timedwait(&m_emptyCond, 0);
            }
        }
    }

    Channel<Type> &operator<<(const Type data) {
        Put(data);
        return *this;
    }

    Channel<Type> &operator>>(Type &data) {
        data = Get();
        return *this;
    }
};

typedef std::function<void()> task_fun_t;
struct __go {
    explicit __go(int stackSize = 0) : m_stackSize(stackSize), m_shareStack(nullptr) {}

    explicit __go(share_stack_t *stack) : m_stackSize(0), m_shareStack(stack) {}

    static void uthreadCallback(void *arg) {
        task_fun_t func = *(task_fun_t*) (arg);
        func();
    }

    inline void operator+(task_fun_t const &arg) {
        uthread_t *ut;
        uthread_attr_t attr;
        if (m_shareStack != nullptr) {
            attr.share_stack = m_shareStack;
        } else {
            attr.stack_size = m_stackSize;
        }
        task_fun_t *args = new task_fun_t(arg);
        cr_create(ut, &attr, &__go::uthreadCallback, args);
        cr_resume(ut);
        delete args;
        cr_release(ut);
    }

    int m_stackSize;
    share_stack_t *m_shareStack;
};

template<typename Ret>
struct __async_wait {
    Ret m_ret;

    typedef std::function<Ret()> async_fun_t;

    __async_wait() {}

    static void uthreadCallback(void *arg) {
        void **args = (void **) arg;
        async_fun_t func = *(async_fun_t *)(args[0]);
        __async_wait *self = (__async_wait *) (args[1]);
        self->m_ret = func();
    }

    inline Ret operator+(std::function<Ret()> const &arg) {
        uthread_t *ut;

        void **args = new void *[2];
        args[0] = new async_fun_t(arg);
        args[1] = this;
        cr_create(ut, nullptr, &__async_wait::uthreadCallback, args);
        cr_resume(ut);

        delete (async_fun_t*)args[0];
        delete[] args;
        cr_release(ut);
        return m_ret;
    }
};

int cr_alloc_local_id();

template<typename Type>
struct UthreadLocal {
    const int m_varId;

    explicit UthreadLocal() : m_varId(cr_alloc_local_id()) {}

    Type *operator->() {
        uthread_t *ut = cr_get_curr_uthread();
        if (ut == nullptr) {
            return nullptr;
        }

        Type *val = (Type *) cr_get_sepc(ut, m_varId);
        if (val == nullptr) {
            val = new Type;
            cr_set_spec(ut, m_varId, val);
        }
        return val;
    }

    void release() {
        uthread_t* ut = cr_get_curr_uthread();
        if(ut == nullptr) {
            return;
        }
        Type *val = (Type *) cr_get_sepc(ut, m_varId);
        if (val != nullptr) {
            delete val;
            cr_set_spec(ut, m_varId, nullptr);
        }
    }
};

#define cr_go __go()+

#define cr_async_wait(type) __async_wait<type>()+

#define cr_local_var(type, name)\
static thread_local UthreadLocal<type> name;

#endif //LIBCR_LIBCR_H

